classdef Exception
    properties (SetAccess=immutable)
        ThrowAs
        Message
        IsHeader
    end
    
    
    
    
    properties (Dependent, SetAccess=immutable)
        Identifier
    end
    
    
    
    
    properties (Abstract, Constant)
        CLASS_IDENTIFIER
    end
    
    
    
    
    properties (Constant)
        IRIS_IDENTIFIER = 'IRISToolbox';
        HIGHLIGHT = '*** '
        BASECLASS_ERROR_HEADER_FORMAT = 'IRIS Toolbox Error'
        BASECLASS_WARNING_HEADER_FORMAT = 'IRIS Toolbox Warning'
        MAX_LEN = 20;
        CONTINUATION_CHAR_CODE = 8230;
        
        ALT2STR_FORMAT = '#%g';
        ALT2STR_FROM_TO_STRING = '-';
        ALT2STR_DEFAULT_LABEL = 'P';        
    end
    
    
    
    
    methods
        function this = Exception(throwAs, message, isHeader)
            this.ThrowAs = throwAs;
            this.Message = message;
            try
                this.IsHeader = isHeader;
            catch
                this.IsHeader = true;
            end
        end
        
        
        
        
        function throw(this, varargin)
            import exception.Exception;
            header = '';
            if this.IsHeader
                header = createHeader(this);
            end
            message = this.Message;
            if true % ##### MOSW
                message = strrep(message, '$ENGINE$', 'Matlab');
            else
                message = strrep(message, '$ENGINE$', 'Octave'); %#ok<UNRCH>
            end
            switch this.ThrowAs
                case 'error'
                    Exception.throwAsError( ...
                        this.Identifier, ...
                        header, ...
                        message, ...
                        varargin{:} );
                case 'warning'
                    Exception.throwAsWarning( ...
                        this.Identifier, ...
                        header, ...
                        message, ...
                        varargin{:} );
            end
        end
        
        
        
        
        function header = createHeader(this)
            import exception.Exception;
            switch this.ThrowAs
                case 'error'
                    header = Exception.BASECLASS_ERROR_HEADER_FORMAT;
                case 'warning'
                    header = Exception.BASECLASS_WARNING_HEADER_FORMAT;
            end
            % header = [ header, ' [', this.Identifier,']' ];
        end
        
        
        
        
        function throwCode(this, varargin)
            import exception.Exception;
            BR = sprintf('\n');
            varargin = strrep(varargin, BR, ' '); % Replace line breaks with spaces.
            varargin = regexprep(varargin, '[ ]{2,}', ' '); % Replace multiple spaces with one space.
            for i = 1 : length(varargin)
                if length(varargin{i})>Exception.MAX_LEN
                    varargin{i} = [ ...
                        varargin{i}(1:Exception.MAX_LEN), ...
                        char(Exception.CONTINUATION_CHAR_CODE), ...
                        ];
                end
            end
            varargin = strtrim(varargin);
            throw(this,varargin{:});
        end
        
        
        
        
        function identifier = get.Identifier(this)
            identifier = lower( char(this) );            
            pos = strfind(identifier, '_');
            identifier([1, pos+1]) = upper( identifier([1, pos+1]) );
            identifier(pos) = '';
            identifier = [ ...
                this.IRIS_IDENTIFIER, ...
                ':',this.CLASS_IDENTIFIER, ...
                ':',identifier ...
                ];            
        end
    end
    
    
    
    
    methods (Static)
        function [stack, n] = reduceStack( )
            stack = dbstack('-completenames');
            [~, irisFolder] = fileparts(irisget('irisroot'));
            irisFolder = lower(irisFolder);
            n = length(stack);
            while n > 0 && isempty(strfind(lower(stack(n).file), irisFolder))
                n = n - 1;
            end
            stack = stack(n+1:end);
        end
        
        
        
        
        function throwAsError(identifier, header, message, varargin)
            import exception.Exception;
            message = [Exception.HIGHLIGHT, message];
            if ~isempty(varargin)
                message = sprintf([message, '\n'], varargin{:});
                message(end) = '';
            end
            if ~isempty(header)
                message = [header, sprintf('\n'), message];
            end
            stack = Exception.reduceStack( );
            errorStruct = struct( ...
                'identifier', identifier, ...
                'message', message, ...
                'stack', stack ...
                );
            error(errorStruct);
        end
        
        
        
        
        function throwAsWarning(identifier, header, message, varargin)
            import exception.Exception;
            q = warning('query',identifier);
            if ~strcmp(q.state,'on')
                return
            end
            message = [Exception.HIGHLIGHT, message];
            if ~isempty(varargin)
                message = sprintf([message, '\n'], varargin{:});
                message(end) = '';
            end
            if ~isempty(header)
                message = [header, sprintf('\n'), message];
            end
            [~, n] = Exception.reduceStack( );
            q = warning('query', 'backtrace');
            warning('off', 'backtrace');
            warning(identifier, message);
            warning(q);
            dbstack(n-1);
        end
        
        
        
        
        function s = alt2str(altVec,label)
            import exception.Exception;
            try, label; catch, label = Exception.ALT2STR_DEFAULT_LABEL; end %#ok<VUNUS,NOCOM>

            if islogical(altVec)
                altVec = find(altVec);
            end
            altVec = altVec(:).';
            
            s = '';
            if isempty(altVec)
                return
            end
            
            n = length(altVec);
            c = cell(1,n);
            for i = 1 : n
                c{i} = sprintf([ ' ', Exception.ALT2STR_FORMAT ], altVec(i));
            end
            
            % Find continuous ranges; these will be replace with FROM-TO.
            ixDiff = diff(altVec)==1;
            ixDiff1 = [false, ixDiff];
            ixDiff2 = [ixDiff, false];
            inx = ixDiff1 & ixDiff2;
            c(inx) = {'-'};
            s = [c{:}];
            s = regexprep(s, '-+ ', Exception.ALT2STR_FROM_TO_STRING);
            
            % [P#1 #5-#10 #100].
            s = [ '[', label, strtrim(s), ']' ];
        end
        
        
        
        
        function c = alternate(varargin)
            ix = cellfun(@iscellstr,varargin);
            pos = find(ix,1);
            len = length(varargin{pos});
            c = cell(1,nargin*len);
            for i = 1 : nargin
                if ischar(varargin{i})
                    c(i:nargin:end) = varargin(i);
                else
                    c(i:nargin:end) = varargin{i};
                end
            end
        end
    end
end